Streaming Tool
A Tool whose executor is a Flow — it can emit preliminary snapshots before the final value. The LAST emission is what feeds the model on subsequent turns; earlier emissions surface as StreamEvent.ToolResult(preliminary = true) for UI consumption only. Extend this (instead of Tool) when a tool can produce a useful early snapshot — a small summary, count, or status — before the full result is ready.
class LineupTool(private val repo: LineupRepo) :
StreamingTool<LineupQuery, Lineup, AppContext>(
name = "getLineup",
description = "Get sets playing at a stage on a given day",
inputSerializer = serializer(),
outputSerializer = serializer(),
) {
override fun ToolExecutionContext<AppContext>.executeStream(input: LineupQuery) = flow {
emit(repo.fastSummary(input)) // preliminary — UI renders immediately
emit(repo.fullDetails(input)) // final — feeds the model
}
}If the Flow emits zero values, the tool is treated as failed (the agent emits StreamEvent.ToolError).
Constructors
Properties
Few-shot example inputs encoded as JSON strings. Mirrors v6's tool.inputExamples (per historical parity gap #19). Disproportionate quality lift on small models — Gemma 4 E2B's tool-call accuracy notably improves with 2-3 in-tool examples. ToolSet.descriptors inlines these into the description as Example: <json> so every provider sees them (most don't expose a separate examples field).
Application-defined metadata bag. Mirrors v6's tool.metadata (per historical parity gap #34). Opaque to the loop — consumers (logger middleware, telemetry, host-side gating) can read it via ToolSet.byName. Examples: feature-flag key, billing tier, owning team. Empty by default.
Provider-specific config for this tool, sent to the model on the wire (e.g. Anthropic cache_control). Distinct from metadata, which is host-side only. Mirrors v6's tool.providerOptions. Empty by default.
Functions
Run the tool — the common single-value case. The model's JSON input has already been deserialized into TInput; return the typed output. this is the ToolExecutionContext, so the typed context, abortSignal, toolCallId, stepNumber, messages, and writer are in scope.
Run the tool, emitting preliminary snapshots; the LAST emission is the final result. this is the ToolExecutionContext.
Approval gate (parity gap #17). Return true to require host approval before execute runs: the loop ends with the call surfaced in GenerateResult.pendingApprovals until the host resumes. The options carry toolCallId + messages + experimental_context, so the gate can decide from conversation history or call identity. Default: never gates.
Lifecycle hook (gap #18) — fired as the input JSON streams in token-by-token (StreamEvent.ToolInputDelta). The accumulated input isn't yet valid JSON; this is for raw-character pre-warm. No-op by default.
Lifecycle hook (gap #18) — fired when the model commits to calling this tool (StreamEvent.ToolInputStart). Pre-warm here: UI spinner, cache priming, etc. No-op by default.
Model-visible summary (parity gaps #17 + #14). Return a structured ToolResultOutput (Text / Json / Error / …) to send the model a short summary in place of the full (possibly verbose) output; the loop bridges it to ContentPart.ToolResult.modelVisible (+ isError). Return null (default) to send the raw output as-is.